import bge
import random
import math
import random
from mathutils import Vector, Matrix


### init method for emitter object. is used for setting parameters
def init(c):
	emitter = emitterObject(c.owner)
	emitter.particleGravity(emitter["ee_gravity"]/50)
	emitter.particleHalo(emitter["ee_halo"])
	emitter.addParticles(emitter["ee_amount"])  
	emitter.particleProxies(eval(emitter["ee_proxies"]))
	emitter.particleLifetime(emitter["ee_lifetime"])  
	emitter.particleEmissionRate(emitter["ee_emission_rate"])  
	emitter.emissionRange((emitter["ee_emission_range_x"],emitter["ee_emission_range_y"],emitter["ee_emission_range_z"]))
	emitter.emissionAngle(emitter["ee_emission_angle_x"],emitter["ee_emission_angle_y"])
	emitter.rotation(emitter["ee_rotation"])
	emitter.randomOrientation(emitter["ee_random_orientation"])
	emitter.randomMovement(emitter["ee_random_movement"])
	emitter.randomSpeed(emitter["ee_random_speed"])
	emitter.particleSpeed(emitter["ee_speed"])
	emitter.explode(emitter["ee_explode"],emitter["ee_explode_emitter"])
	emitter.culling(eval(emitter["ee_frustum_culling"])[0],eval(emitter["ee_frustum_culling"])[1],emitter["ee_freeze_mode"])
											  
### update method for emitter object. runs every tic
def update(c):
	emitter = c.owner
	emitter.update(emitter["ee_refresh_rate"])
		




### particle object
class particleObject(bge.types.KX_GameObject):
	def __init__(self,obj):
		self.emitter = None
		
		self.lifetime = 0
		self.lifeTicker = 0
		self.waitTicker = 0
		self.colorIdx = 0
		
		self.speed = 0
		self.startSpeed = 0
		self.endSpeed = 0
		
		self.randomSpeed = 0
		self.randTime = 0
		
		self.haloRotation = 0
		
		self.rotationX = 0
		self.rotationY = 0
		self.rotationZ = 0
		
		self.scale = 0
		self.startScale = 0
		self.endScale = 0
		
		
	

### emitter object	
class emitterObject(bge.types.KX_GameObject):
	def __init__(self,obj):
		
		self.gameObject = obj
		self.curScene = bge.logic.getCurrentScene()
		self.cam = self.curScene.active_camera
		
		self.cullingOn = True
		self.cullingRadius = 1
		
		self.particleList = []
		self.particleProxy = []
		self.killEmitter = False
		self.child = None
		self.freeze = False
		self.pause = False
		self.freezeMode = 0 # 0->Emitter gets freezed, 1->Emitter gets paused. Particles will still run to teir end.
		self.halo = False
		
		self.pCounter = float(0)
		self.pCounterOld = self.pCounter
		
		self.pEmissionRate = 60
		
		self.pAmount = 0
		self.pAmountStart = 0
		self.pSpeed = 0
		self.pStartSpeed = 0
		self.pEndSpeed = 0
		self.pLifetime = 10
		self.pAngle = []
		self.pRandomRot = 0
		self.pRotation = 0
		self.pRandomOrientation = True
		self.pRandomSpeed = 0
		
		self.pGravity = 0
		
		self.pSpeed = [[-0.1, 0], [0.1, 100]]#[[0.3,0.0],[0.3,0.6],[-0.3,0.8],[0.0,1.0]]

		
		self.speedCacheProgress = -1
		self.speedCache = []

		self.ticker = 0
		self.emitRange = []
		self.stopEmitter = False	
		
		self.colorRamp = []
		self.colorRampIdx = 0
		
		self.pExplode = False
		self.pExplodeEmitter = ""
		
		#self.oldEndObject = self.endObject()
	
	
	def endObject(self):
		for particle in self.particleList:
			particle.endObject()
		super(emitterObject,self).endObject()
		
	### set variables for the particles
	def particleGravity(self,pGravity):
		self.pGravity = pGravity
	
	def particleHalo(self,halo):
		self.halo = halo
	
	def particleRandomLifetime(self,pRandomLifetime):
		self.pRandomLifetime = random.randint(-pRandomLifetime/2,pRandomLifetime/2)
	
	def particleLifetime(self,pLifetime):
		self.pLifetime = pLifetime
		
	def particleEmissionRate(self,pEmissionRate):
		self.pEmissionRate = pEmissionRate
		
	def addParticles(self,pAmount):
		self.pAmount = pAmount
		self.pAmountStart = pAmount
		
	def particleSpeed(self,pSpeed):
		self.pSpeed = eval(pSpeed)
		
	def emissionRange(self,emitRange):
		self.emitRange = emitRange
	
	def particleProxies(self,particleProxy):
		self.particleProxy = particleProxy
	
	def emissionAngle(self,pAngleX,pAngleY):
		self.pAngle = [pAngleX,pAngleY]
		
	def randomMovement(self,pRandomRot):
		self.pRandomRot = pRandomRot
		
	def randomSpeed(self,pRandomSpeed):
		self.pRandomSpeed = pRandomSpeed
		
	def rotation(self,pRotation):
		self.pRotation = pRotation	
	
	def randomOrientation(self,pRandomOrientation):
		self.pRandomOrientation = pRandomOrientation
		
	def explode(self,pExplode,pExplodeEmitter):
		self.pExplode = pExplode
		self.pExplodeEmitter = pExplodeEmitter
	def culling(self,cullingOn,cullingRadius,freezeMode):
		self.cullingOn = cullingOn
		self.cullingRadius = cullingRadius
		if freezeMode == "Pause":
			self.freezeMode = 1
		elif freezeMode == "Freeze":
			self.freezeMode = 0
	
	### pause emission
	def pauseEmitter(self):
		if not self.stopEmitter:
			self.stopEmitter = True
			self.pAmount = self.pAmountStart
	
	### freeze emitter
	def freezeEmitter(self,var):
		self.freeze = var
	
	### resume emission
	def startEmitter(self):
		if not self.killEmitter:
			self.stopEmitter = False	
	
	def endEmitter(self):
		self.killEmitter = True
	
	### spawn particle
	def spawnParticle(self,ticker):
		### particle emission rate
		self.pCounter += (self.pEmissionRate/60)
		emitcount =  int(self.pCounter) - int(self.pCounterOld) 
		for x in range(0,emitcount):
			if self.pAmount == 0:
				break
			
			### particles are spawned,setup and added to the particleList
			particleName = random.choice(self.particleProxy)
			
			### create particle parent that is moved
			particle = self.scene.addObject("particleParent",self,0)
			particle.localScale = (1,1,1)
			particle = particleObject(particle)
			particle.emitter = self
			
			### create particle child. This is the billboard object
			particle.child = self.scene.addObject(particleName,self,0)
			particle.child.setParent(particle,True,True)
			### set particle variables for calculation
			particle.startSpeed = self.pStartSpeed
			particle.endSpeed = self.pEndSpeed
			
			particle.randomSpeed = random.uniform(1-self.pRandomSpeed,1+self.pRandomSpeed)
			if particle.randomSpeed < 0:
				particle.randomSpeed = 0
			
			particle.child.playAction(self.name,1000,1500,speed=1.0)
			particle.lifetime = self.pLifetime
			particle.haloRotation = random.choice([-self.pRotation,self.pRotation])
			
			random_rot = math.radians(random.randint(0,360))
			
			if self.pRandomOrientation:
				particle.child.applyRotation(Vector((random_rot,0,0)),True)
			
			randomPos = Vector((random.uniform(-self.emitRange[0],self.emitRange[0]),random.uniform(-self.emitRange[1],self.emitRange[1]),random.uniform(-self.emitRange[2],self.emitRange[2])))
			particle.localPosition += randomPos*self.localOrientation.inverted()
			self.calcParticleOrientation(particle)
			self.particleList.append(particle) 
			self.pCounterOld = self.pCounter
			particle.child.alignAxisToVect(self.cam.position,0,1.0)
			### Substract Emission Count
			self.pAmount -= 1
	
	### def create Particle System on Particle Death
	def explodeOnDeath(self,particle,emitter):	
		emitter = self.curScene.addObject(emitter,particle,0)
		emitter = (emitterObject(emitter))
	
	### delete particleObject and list entry
	def endParticle(self,particle):	
		self.particleList.remove(particle)
		particle.endObject()
	
	### delete emitter
	def deleteEmitter(self):
		self.stopEmitter = True
		if len(self.particleList) == 0:
			self.endObject()
			
	   
	### calculate the speed of a particle
	def calcParticleSpeed(self,particle,idx):		
		if self.speedCacheProgress < idx+1:
			
			def inOutSine(t, b, c, d):
				return -c / 2 * (math.cos(math.pi * t / d) - 1) + b
			def inOutQuint(t, b, c, d):
				t = t / d * 2
				if t < 1:
					return c / 2 * math.pow(t, 5) + b
				else:
					t = t - 2
				return c / 2 * (math.pow(t, 5) + 2) + b
			def inOutLin(t, b, c, d):
				return t / d * c + (1.0 - t / d) * b
			def current_speed(time, speeds):
				if len(speeds) == 0:
					return 0.0
				t = 0.0
				b = 0.0
				c = 0.0
				d = 0.0
				for i, x in enumerate(speeds):
					if x[1] > time:
						if i == 0:
							t = time
							b = 0.0
							c = x[0]
							d = x[1]
						else:
							prev = speeds[i-1]
							t = time-prev[1]
							b = prev[0]
							c = x[0]
							d = x[1] - prev[1]
						factor = inOutSine(t,0.0,1.0,d)
						return c * factor + b * (1.0 - factor)
				return speeds[len(speeds) - 1][0]	  
			
			time = particle.lifeTicker/self.pLifetime*self.tic
			particle.speed = current_speed(time,self.pSpeed)*self.tic
			particle.applyMovement(Vector((0,0,particle.speed)),True)
			self.speedCache.append(particle.speed)
			self.speedCacheProgress += self.tic
			
			### gravity calculation
			gravity = self.pGravity * time
			particle.applyMovement(Vector((0,0,gravity)),False)
		else:
			particle.applyMovement(Vector((0,0,self.speedCache[int(idx/self.tic)]*particle.randomSpeed)),True)
			
			### gravity calculation
			time = particle.lifeTicker/self.pLifetime*self.tic
			gravity = self.pGravity * time
			particle.applyMovement(Vector((0,0,gravity)),False)

	### calculate particle orientation
	def calcParticleOrientation(self,particle):	
		particle.applyRotation((random.uniform(-self.pAngle[0],self.pAngle[0]),random.uniform(-self.pAngle[1],self.pAngle[1]),0),True)	
		
	### calculate random movement
	def calcRandomMovement(self,particle,idx):
		particle.rotationX += random.uniform(-self.pRandomRot,self.pRandomRot)
		particle.rotationY += random.uniform(-self.pRandomRot,self.pRandomRot)
		#particle.rotationZ += random.uniform(-self.pRandomRot,self.pRandomRot)
		particle.applyRotation((particle.rotationX* self.tic,particle.rotationY* self.tic,0)) 
		
		if self.halo:
			particle.child.alignAxisToVect(self.cam.position,0,1.0)
		particle.child.applyRotation(Vector((particle.haloRotation,0,0)),True)
	
	### function for emitter culling
	def checkCulling(self,cam):
		if self.cullingOn:
			if (cam.sphereInsideFrustum(self.position, self.cullingRadius) == cam.OUTSIDE) and self.freezeMode == 0:
				self.freeze = True
			else:
				self.freeze = False
			if (cam.sphereInsideFrustum(self.position, self.cullingRadius) == cam.OUTSIDE) and self.freezeMode == 1:
				self.pause = True
			else:
				self.pause = False
			
			
	### update method that is run every tic
	def update(self,tic):
		### frustum culling for particle emitter
		self.checkCulling(self.cam)
		
		### if freeze is True, particle Action frame gets lockes
		if self.freeze:
			for i, particle in enumerate(self.particleList):
				frame = particle.child.getActionFrame(0)
				particle.child.setActionFrame(frame,0)
		
		### if freeze is False Particles will be calculated otherwise they are freezed
		else:
			### update is executed on specified ticrate
			self.tic = tic
			self.ticker += 1
			
			### if variable is True, emission of new particles is stopped, otherwise it is resumed
			if not self.stopEmitter:
				if not self.pause:
					if self.pAmount != 0:
						self.spawnParticle(self.ticker)
					else:
						self.killEmitter = True  
			
			if self.killEmitter:
				self.deleteEmitter()		
			
		
			if self.ticker%self.tic == 0:
				### find active camera
				self.cam = self.curScene.active_camera
				
				### loops through all particles in the emitter particleList and runs logic for them
				for i, particle in enumerate(self.particleList):
					particle.lifeTicker += 1
					particle.waitTicker += 1
					particle.lifetime -= self.tic
					idx = int(abs(self.pLifetime-particle.lifetime))
					### logic that is executed while lifetime is not exceeded
					if particle.lifetime > 0:
						### Color and Scaling calculation is done via action playback
						self.calcParticleSpeed(particle,idx)
						self.calcRandomMovement(particle,idx)
					### when particle lifetime is exceeded, particle gets killed and deleted from the particle list	
					else:
						if self.pExplode:
							try:
								self.explodeOnDeath(particle,self.pExplodeEmitter)			   
							except:
								pass
						self.endParticle(particle) 
		